#include <47func.h>
#include <ctype.h>

typedef struct msg
{
    char command[10];
    int cmdType;
    char arg[100];
} MSG;

typedef struct train_s
{
    int length;
    char data[1000]; // 上限是1000个字节
} train_t;

typedef enum taskType
{
    CD = 1,
    LS = 2,
    PUTS = 3,
    GETS = 4,
    REMOVE = 5,
    PWD = 6,
    MKDIR = 7,
    RMDIR = 8,
    EXIT = 9,
    MYLS = 10, // 本地执行ls
    MYCD = 11  // 本地执行cd
} TaskType;

off_t getOffset(int netfd, const char *filename)
{
    char findFilename[50] = {0};
    strcpy(findFilename, filename);

    // 给文件名加后缀
    strcat(findFilename, ".filepart");

    // 判断当前路径是否存在同名文件
    bool exist = false;
    int ret = open(findFilename, O_RDONLY);
    if (ret != -1)
    {
        exist = true;
    }

    //train_t train;

    // 如果存在同名需要断点续传的文件
    if(exist == true)
    {
        int fd = open(findFilename, O_RDWR);

        // 获取文件大小并发送
        struct stat statbuf;
        fstat(fd, &statbuf);
        //bzero(&train, sizeof(train));
        //train.length = sizeof(statbuf.st_size);
        //memcpy(train.data, &statbuf.st_size, train.length);
        //send(netfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
        send(netfd, &statbuf.st_size, sizeof(statbuf.st_size), MSG_NOSIGNAL);

        return statbuf.st_size;
    }
    else
    {
        // bzero(&train, sizeof(train));
        // train.length = sizeof(off_t);
        off_t offset = 0;
        // memcpy(train.data, &offset, train.length);
        send(netfd, &offset, sizeof(offset), MSG_NOSIGNAL);

        return 0;
    }

}

// 读入一行字符串，用来读命令
int readLine(char *str)
{
    int num = 0;
    char ch;
    while (num < 100 && (ch = getchar()) != '\n')
    {
        str[num++] = ch;
    }
    str[num] = '\0';
    return num;
}

int recvn(int sockfd, void *buf, int length)
{
    int total = 0;
    int ret;
    char *p = (char *)buf;
    while (total < length)
    {
        ret = recv(sockfd, p + total, length - total, 0);
        total += ret;
    }
    return 0;
}

//fd表示文件描述符，res存储文件的md5
// get file MD5
int getmd5(int fd, unsigned char *md5)
{
    char buf[4096];
    unsigned char md[33] = {0};
    MD5_CTX ctx;
    MD5_Init(&ctx);
    while(1)
    {
        bzero(buf, 4096);
        ssize_t sret = read(fd, buf, 4096);
        MD5_Update(&ctx,buf,sret);
        if(sret == 0)
        {
            break;
        }
    }
    MD5_Final(md, &ctx);
    //for(int i = 0; i < 16; i++)
    //{
    //    sprintf("%02x", md5[i]);
    //
    //}
    //printf("\n");
    bzero(md5, sizeof(md5));
    for(int i = 0;i < 16; i++)
    {
        snprintf(md5 + i * 2, 2 + 1, "%02x", md[i]);
    }
    md5[32] = '\0';
    return 0;
}

// 下载文件
int getFile(int sockfd, char *filename, off_t offset)
{
    char oriFilename[50] = {0};
    strcpy(oriFilename, filename);

    train_t train;
    bzero(&train, sizeof(train));
    strcat(filename, ".filepart");

    int fd = open(filename, O_RDWR | O_CREAT, 0666);
    off_t filesize;
    recvn(sockfd, &train.length, sizeof(train.length));
    recvn(sockfd, &filesize, train.length);
    printf("filesize = %ld\n", filesize);
    off_t cursize = 0;
    off_t lastsize = 0;
    off_t slice = filesize / 10000;

    lseek(fd, offset, SEEK_CUR);
    while (1)
    {
        bzero(&train, sizeof(train));
        // recv(sockfd,&train.length,sizeof(train.length),MSG_WAITALL);//先收四个字节
        recvn(sockfd, &train.length, sizeof(train.length)); // 先收四个字节
        if (train.length != 1000)
        {
            printf("train.length = %d\n", train.length);
        }
        if (train.length == 0)
        {
            break;
        }
        // recv(sockfd,train.data,train.length,MSG_WAITALL);//再一个train.length的长度
        recvn(sockfd, train.data, train.length); // 再一个train.length的长度
        write(fd, train.data, train.length);
        cursize += train.length;
        if (cursize - lastsize > slice)
        {
            printf("%5.2lf%%\r", 100.0 * cursize / filesize);
            fflush(stdout);
            lastsize = cursize;
        }
    }
    printf("100.00%%\n");
    rename(filename, oriFilename);
    close(fd);
    return 0;
}


// 发送文件
int putFile(int sockfd, char *filename)
{
    train_t train;
    off_t offset;
    recv(sockfd, &offset, sizeof(offset), 0);

    int fd = open(filename, O_RDWR);
    struct stat statbuf;
    // 获取文件大小
    fstat(fd, &statbuf);
    // 发送小火车 火车头 size的长度 火车车厢 size的内容
    bzero(&train, sizeof(train));
    off_t realSize = statbuf.st_size - offset;

    train.length = sizeof(realSize);
    memcpy(train.data, &realSize, train.length);
    send(sockfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);

    if (realSize > 100 * 1024 * 1024)
    {
        // 如果文件大于100M，使用mmap
        lseek(fd, offset, SEEK_CUR);
        char *p = (char *)mmap(NULL, realSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        ERROR_CHECK(p, MAP_FAILED, "mmap");
        off_t totalsize = 0; // 记录已经发送的长度
        while (totalsize < realSize)
        {
            if (realSize - totalsize > (long)sizeof(train.data))
            {
                train.length = sizeof(train.data); // 剩余数据超过火车车厢的上限
            }
            else
            {
                train.length = realSize - totalsize;
            }
            send(sockfd, &train.length, sizeof(train.length), MSG_NOSIGNAL);
            send(sockfd, p + totalsize, train.length, MSG_NOSIGNAL);
            totalsize += train.length;
        }
        munmap(p, realSize);
    }
    else
    {
        // 小于100M，普通发送
        lseek(fd, offset, SEEK_CUR);
        while (1)
        {
            // 将文件的数据读取到小火车的车厢部分          
            ssize_t sret = read(fd, train.data, sizeof(train.data));
            train.length = sret;
            // 发送小火车
            send(sockfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
            if (sret == 0)
            {
                break;
            }
        }
    }

    // 补发一个空火车
    train.length = 0;
    send(sockfd, &train.length, sizeof(train.length), MSG_NOSIGNAL);
    close(fd);

    return 0;
}

int upload(int sockfd, char* filename, off_t offset, char* usename, char* path)
{
    unsigned char md5[33] = {0};
    int fd = open(filename, O_RDWR);
    // printf("fd = %d\n", fd);
    getmd5(fd, md5);
    puts(md5);
    train_t train;
    bzero(&train, sizeof(train));
    train.length = sizeof(md5);
    memcpy(train.data, md5, train.length);
    send(sockfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
    //bzero(&train, sizeof(train));                                        
    //train.length = strlen(md5);
    // memcpy(train.data, md5, train.length);
    //send(netfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);

    char flag[1] = {0};
    recvn(sockfd, &train.length, sizeof(train.length));
    recvn(sockfd, flag, train.length);

    if(flag[0] == 'y')
    {
        //表示需要传输文件
        putFile(sockfd, filename, offset);
    }

    return 0;
}
void commandParser(int sockfd)
{
    MSG msg; // 接收客户端命令
    char str[64];
    char *token;
    int argCount;
    train_t train;
    char buf[4096];
    while (readLine(str))
    {
        token = NULL;
        memset(&train, 0, sizeof(train_t));
        memset(&msg, 0, sizeof(MSG));
        memset(&buf, 0, sizeof(buf));
        argCount = 0;
        token = strtok(str, " ");
        strcpy(msg.command, token);
        // puts(msg.command);
        token = strtok(NULL, " ");
        if (token)
        { // 获取命令后的参数
            strcpy(msg.arg, token);
            argCount++;
        }
        memset(str, 0, sizeof(str));
        printf("msg.command = %s\n", msg.command);
        printf("msg.arg = %s\n", msg.arg);
        printf("argCount = %d\n", argCount);
        // 开始解析命令
        if (strcmp(msg.command, "cd") == 0)
        { // 执行cd命令
            if (argCount == 0 || argCount > 1)
            { // cd后无参数或有多个参数  无效命令
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = CD;
                send(sockfd, &msg, sizeof(msg), 0);
                recv(sockfd, buf, sizeof(buf), 0);
                printf("%s\n", buf);
            }
        }
        else if (strcmp(msg.command, "ls") == 0)
        {                     // ls命令
            msg.cmdType = LS; // 纠正错误
            if (argCount == 0 || argCount == 1)
            {
                // 无参 给服务端发送显示当前目录的请求
                // 有一个参数，将命令发送给服务端，若命令合法则显示指定目录
                send(sockfd, &msg, sizeof(msg), 0);
                int ret = recv(sockfd, buf, sizeof(buf), 0);
                printf("ret = %d, buf = %s\n", ret, buf);
            }
            else
            {
                printf("args error\n");
                continue;
            }
        }
        else if (strcmp(msg.command, "puts") == 0)
        { // puts命令
            if (argCount != 1)
            { // puts后仅可接收一个参数
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = PUTS;
                send(sockfd, &msg, sizeof(msg), 0);
                //路径怎么获取？这里先把文件名当做参数传入
                //用户名怎么获取？先测试为“A”
                char usename[10] = "A";
                char path[100] = "root";
                //printf("filename = %s\n", msg.arg);
                upload(sockfd, msg.arg, getOffset(sockfd, msg.arg), usename, path);
            }
        }
        else if (strcmp(msg.command, "gets") == 0)
        { // gets命令
            if (argCount != 1)
            { // gets后仅可接收一个参数
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = GETS;
                send(sockfd, &msg, sizeof(msg), 0);
                off_t offset = getOffset(sockfd, msg.arg);
                getFile(sockfd, msg.arg, offset);
            }
        }
        else if (strcmp(msg.command, "remove") == 0)
        { // remove命令
            if (argCount != 1)
            { // remove后仅可接收一个参数
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = REMOVE;
                send(sockfd, &msg, sizeof(msg), 0);
            }
        }
        else if (strcmp(msg.command, "pwd") == 0)
        { // pwd命令
            if (argCount != 0)
            {
                printf("args error\n");
                continue;
            }
            else
            {
                // 将请求发送给服务端执行
                msg.cmdType = PWD;
                send(sockfd, &msg, sizeof(msg), 0);
                // 此处有问题
                int ret = recv(sockfd, buf, sizeof(buf), 0);
                printf("ret = %d, pwd: %s\n", ret, buf);
            }
        }
        else if (strcmp(msg.command, "mkdir") == 0)
        { // mkdir命令
            if (argCount != 1)
            {
                printf("args error\n");
                continue;
            }
            else
            {
                msg.cmdType = MKDIR;
                send(sockfd, &msg, sizeof(msg), 0);
            }
        }
        else if (strcmp(msg.command, "rmdir") == 0)
        { // rmdir命令
            if (argCount != 1)
            {
                printf("args error\n");
                continue;
            }
            else
            {
                // 请求交给服务器  需要哦安短目录是否为空
                msg.cmdType = RMDIR;
                send(sockfd, &msg, sizeof(msg), 0);
            }
        }
        else if (strcmp(msg.command, "exit") == 0)
        {
            // exit模块目前暂时有问题 但可用
            // if(argCount != 0)
            //     continue;
            msg.cmdType = EXIT;
            send(sockfd, &msg, sizeof(msg), 0);
            // recv(sockfd, buf, sizeof(buf), 0);
            // printf("%s\n", buf);
            exit(0);
        }
        else if (strcmp(msg.command, "myls") == 0)
        {
            system("ls");
            continue;
        }
        else if (strcmp(msg.command, "mycd") == 0)
        {
            if (argCount == 0)
            { // 非法参数
                printf("argc error!\n");
            }
            else
            {
                int ret = chdir(msg.arg);
                if (ret == 0)
                { // 执行成功
                    getcwd(buf, sizeof(buf));
                    printf("cwd: %s\n", buf);
                }
                else
                { // 执行失败
                    printf("No such directory or file!\n");
                }
            }
            continue;
        }
        else
        {
            continue;
        }
    }
}



int main(int argc, char *argv[])
{
    // ./client 192.168.118.128 1234 filename

    ARGS_CHECK(argc, 3);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serverAddr.sin_port = htons(atoi(argv[2]));
    int ret = connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    ERROR_CHECK(ret, -1, "connect");

    //输入并校验用户信息 
    //    MSG userInfo;
    //    int loginFlag; //-1为失败 0为成功
    //    int loginNum = 3;   //允许输入3次i
    //    while(loginNum--){
    //        loginFlag = -1;
    //        memset(&userInfo, 0, sizeof(MSG));
    //        scanf("%s %s", userInfo.command, userInfo.arg);
    //        send(sockfd, &userInfo, sizeof(MSG), 0);
    //        recv(sockfd, &loginFlag, sizeof(int), 0);
    //        if(loginFlag == 0){
    //            break;
    //        }
    //    }
    //    if(loginFlag == 0)  //用户名密码验证通过
    //    {   
    //        //连接数据库
    //        MYSQL *db = mysql_init(NULL); //NULL自动分配内存
    //        const char* host = "localhost";
    //        const char* user = "root";
    //        const char* password = "123456";
    //        const char* database = "47test";
    //        MYSQL* ret = mysql_real_connect(db,host,user,password,database, 0, NULL, 0);
    //        if(ret == NULL)
    //        {
    //            printf("Error: %s\n", mysql_error(db));
    //            return -1;
    //        }
    //
    //       commandParser(sockfd);
    //
    //        //关闭数据库
    //        mysql_close(db);
    //    }
    //    else
    //        printf("Try with wrong username or password 3 times. Unable to login!\n");
    //commandParser(sockfd);

    ////连接数据库
    MYSQL *db = mysql_init(NULL); //NULL自动分配内存
    const char* host = "localhost";
    const char* user = "root";
    const char* password = "123456";
    const char* database = "47test";
    MYSQL* mret = mysql_real_connect(db,host,user,password,database, 0, NULL, 0);
    if(mret == NULL)
    {
        printf("Error: %s\n", mysql_error(db));
        return -1;
    }

    commandParser(sockfd);
    //
    ////关闭数据库
    mysql_close(db);

    return 0;
}
